﻿using OverStore.Runtime.Storage;
using OverStore.Runtime.ObjectRepository;

namespace OverStore.Runtime.ObjectRepository
{
    /// <summary>
    /// Provide access to instance of specified type by identifier and
    /// provider access to identifier by instance and updating identifier of instance.
    /// <seealso cref="IRepository{TInstance}"/>
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    /// <typeparam name="TIdentifier">The type of the instance identifier.</typeparam>
    /// <remarks>
    /// <para>
    /// IRegistry&lt;TInstance, TIdentifier&gt; is very important interface allows to share persisting functionallity between components. 
    /// IRegistry exposes set of methods for get instance of specific type by identifier, get identifier by instance and update instance 
    /// identifier.
    /// </para>
    /// <para>Don't create instances of types implements IRegistry interface directly, instead use OverStore.Runtime.PersistenceServiceProvider.IPersistenceServiceProvider 
    /// interface for obtain instance of IRegistry for specific type.</para>
    /// <para>
    /// IRegistry is one of two interfaces exposes persistence functionallity of any supported type to any runtime component. The second one is 
    /// IRepository, that allows reloading, saving and deleting object data. Both these interfaces, and 
    /// OverStore.Runtime.PersistenceServiceProvider.IPersistenceServiceProvider interface are named persistence services.
    /// </para>
    /// </remarks>
    public interface IEntityRegistry<TInstance, TIdentifier> : IRepository<TInstance>
        where TInstance : class
    {
        /// <summary>
        /// Get identifier for object registered in session.
        /// If object is not registered in session, it will be placed in session.
        /// If object does not have identifier yet, the identifier will be assigned to it
        /// corresponding to identification rules.
        /// </summary>
        /// <param name="instance">The instance to get identifier.</param>
        /// <returns>The instance identifier.</returns>
        /// <exception cref="PersistingRuntimeException">Error occured during get identifier.</exception>
        TIdentifier GetIdentifier(TInstance instance);

        /// <summary>
        /// Remember new identifier for instance registered in session.
        /// Call AcceptChanges method to actually apply new identifier to instance and
        /// register new identifier in session.
        /// </summary>
        /// <param name="instance">The instance to update its identifier.</param>
        /// <param name="newIdentifier">The new identifier.</param>
        /// <exception cref="PersistingRuntimeException">Error occured during update identifier.</exception>
        /// <remarks>
        /// Identifier updating is required if identifiers are generated by database.
        /// Delayed identifier updating is required because if error occured due updating the instance
        /// or other instance in batch, identifier value generated by database becomes invalid.
        /// </remarks>
        void PutNewIdentifier(TInstance instance, TIdentifier newIdentifier);

        /// <summary>
        /// Gets entity with specified identifier. If entity registered in session it will be returned.
        /// Otherwise it will loaded from data storage. If there is no entity with specified identifier in data storage,
        /// method returns default value of <typeparamref name="TInstance"/> type.
        /// </summary>
        /// <param name="identifier">The identifier of entity to get from registry.</param>
        /// <returns>The entity with specified identifier or default value of <typeparamref name="TEntity"/> type of there is no
        /// entity with specified identifier exists in data storage.</returns>
        TInstance GetInstanceOrDefault(TIdentifier identifier);

        /// <summary>
        /// Get object registered in session.
        /// If object is not found, the object will be constructed, registered in session, reloaded and
        /// returned.
        /// If object does not registered in session, it will be constructed
        /// using data extracted from default gateway.
        /// </summary>
        /// <param name="identifier">The identifier of instance to get.</param>
        /// <returns>The instance with specified identifier.</returns>
        /// <exception cref="PersistingRuntimeException">Error occured during get object by identifier.</exception>
        TInstance GetInstance(TIdentifier identifier);

        /// <summary>
        /// Get object registered in session.
        /// If object is not found, the object will be constructed, registered in session and reloaded
        /// using data passed as <paramref name="data"/> argument.
        /// If <paramref name="data"/> is null, the object will be constructed using data extracted from default gateway.
        /// </summary>
        /// <param name="identifier">The identifier of instance to get.</param>
        /// <param name="data">The data used for object construction or null for using data from default gateway.</param>
        /// <returns>The instance with specified identifier.</returns>
        /// <exception cref="PersistingRuntimeException">Error occured during get object by identifier.</exception>
        TInstance GetInstance(TIdentifier identifier, TypedRecord data);
    }
}